using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using BizTalk.BuildGenerator.MetaData;
using BizTalk.BuildGenerator.MetaData.ResourceAdapters;
using Microsoft.BizTalk.ApplicationDeployment;
using System.Reflection;
using Microsoft.BizTalk.ExplorerOM;
using Application = Microsoft.BizTalk.ExplorerOM.Application;

namespace BizTalk.BuildGenerator
{
    /// <summary>
    /// Helper class for getting applications
    /// </summary>
    public sealed class BizTalkHelper
    {
        /// <summary>
        /// The name of the management db
        /// </summary>
        private static string BizTalkManagementDatabaseName
        {
            get
            {
                return Properties.Settings.Default.BizTalkManagementDatabaseName;
            }
        }
        /// <summary>
        /// The name of the database server
        /// </summary>
        private static string BizTalkDatabaseServerName
        {
            get
            {
                return Properties.Settings.Default.BizTalkDatabaseServer;
            }
        }

        /// <summary>
        /// Gets a list of the adapters in BizTalk
        /// </summary>
        /// <returns></returns>
        public static List<string> GetAdapters()
        {
            var adapters = new List<string>();
            using (var bizTalkGroup = new Group())
            {
                bizTalkGroup.DBName = BizTalkManagementDatabaseName;
                bizTalkGroup.DBServer = BizTalkDatabaseServerName;

                adapters.AddRange(from ProtocolType protocol in bizTalkGroup.CatalogExplorer.ProtocolTypes select protocol.Name);
            }
            return adapters;
        }
        /// <summary>
        /// Gets all of the applications deployed on the server
        /// </summary>        
        /// <returns></returns>
        public static List<string> GetApplications()
        {            
            var applicationNames = new List<string>();
            using (var group = new Group())
            {
                group.DBName = BizTalkManagementDatabaseName;
                group.DBServer = BizTalkDatabaseServerName;

                //Debug.WriteLine(AppDomain.CurrentDomain.
                var explorer = group.CatalogExplorer;
                applicationNames.AddRange(
                    explorer.Applications.Cast<Application>().Select(application => application.Name));
            }
            return applicationNames;
        }       
        /// <summary>
        /// Describes the application
        /// </summary>
        /// <param name="applicationName"></param>
        /// <returns></returns>
        public static ApplicationDescription DescribeApplication(string applicationName)
        {
            var applicationDescription = new ApplicationDescription();

            using (var group = new Group())
            {
                group.DBName = BizTalkManagementDatabaseName;
                group.DBServer = BizTalkDatabaseServerName;
                var app = group.Applications[applicationName];

                if (app != null)
                {
                    applicationDescription.Name = app.Name;
                    applicationDescription.Description = app.Description;

                    //Add application references
                    if (app.References != null)
                    {
                        foreach (var referencedApp in app.References)
                        {
                            applicationDescription.ReferencedApplications.Add(referencedApp.Name);
                        }
                    }

                    foreach (Resource resource in app.ResourceCollection)
                    {
                        Debug.WriteLine("Resource Type: " + resource.ResourceType);
                        var applicationResource = new ApplicationResource
                                                      {
                                                          FullName = resource.Luid,
                                                          Type = GetResourceType(resource.ResourceType)
                                                      };

                        if (!IncludeResource(applicationResource)) continue;
                        AddResourceReferences(resource, applicationResource);
                        AddResourceProperties(resource, applicationResource);
                        applicationDescription.Resources.Add(applicationResource);
                    }
                }

                AddDependantResources(applicationDescription);

                SortResources(applicationDescription);
            }
            return applicationDescription;
        }

        #region Private Methods
        /// <summary>
        /// Returns just the stuff after the : in the type name
        /// </summary>
        /// <param name="typeDescription"></param>
        /// <returns></returns>
        private static string GetResourceType(string typeDescription)
        {
            var splitPosition = typeDescription.IndexOf(":");
            return typeDescription.Substring(splitPosition + 1);
        }
        /// <summary>
        /// Sorts the resources based on their dependancies.  They need to be sorted so when we generate a script to deploy they will
        /// be loaded to BizTalk in the correct order
        /// </summary>
        /// <param name="applicationDescription"></param>
        private static void SortResources(ApplicationDescription applicationDescription)
        {
            ApplicationResourceSorter.Sort(applicationDescription.Resources);
            //applicationDescription.Resources.Sort();
            Trace.WriteLine("The resource order after Sort is:");
            foreach (var r in applicationDescription.Resources)
                Trace.WriteLine(r.FullName);
        }
        /// <summary>
        /// Loads up the resource properties
        /// </summary>
        /// <param name="resource"></param>
        /// <param name="applicationResource"></param>
        private static void AddResourceProperties(IDeploymentResource resource, ApplicationResource applicationResource)
        {
            if (resource == null) throw new ArgumentNullException("resource");

            var enumerator = resource.Properties.GetEnumerator();
            while (enumerator.MoveNext())
            {
                Debug.WriteLine(enumerator.Current.Key + ":" + Convert.ToString(enumerator.Current.Value));
                var property = new ResourceProperty {Name = enumerator.Current.Key, Value = enumerator.Current.Value};
                applicationResource.Properties.Add(property);
            }
        }
        /// <summary>
        /// Uses reflection to load the references of each application resource
        /// </summary>
        /// <param name="applicationResource"></param>
        /// <param name="resource"></param>
        private static void AddResourceReferences(IDeploymentResource resource, ApplicationResource applicationResource)
        {
            if (resource == null) throw new ArgumentNullException("resource");
            if (applicationResource == null) throw new ArgumentNullException("applicationResource");

            const string sourceLocationPropertyName = "SourceLocation";

            if (applicationResource.Type != ResourceTypes.Assembly &&
                applicationResource.Type != ResourceTypes.BizTalkAssembly) return;
            var location = (string)resource.Properties[sourceLocationPropertyName];
            var a = Assembly.LoadFile(location);
            foreach (var name in a.GetReferencedAssemblies())
            {
                Trace.WriteLine(string.Format("Reflected Assembly: {0} depends on {1}", a.FullName, name.FullName));
                applicationResource.References.Add(name.FullName);
            }
        }
        /// <summary>
        /// Looks at the references for each resource and creates links between resources based on the references.  This then allows the resources to be later sorted based on these dependancies
        /// </summary>
        /// <param name="applicationDescription"></param>
        private static void AddDependantResources(ApplicationDescription applicationDescription)
        {            
            foreach (var resource in applicationDescription.Resources)
            {
                Trace.WriteLine("");
                Trace.WriteLine(resource.FullName);
                Trace.WriteLine("Depends on:");
                var resource1 = resource;
                var resource2 = resource;
                foreach (var dependantResource in
                    applicationDescription.Resources.Where(dependantResource => dependantResource.FullName != resource2.FullName).Where(dependantResource => resource1.References.Contains(dependantResource.FullName)))
                {
                    Trace.WriteLine(dependantResource.FullName);
                    resource.DependantResources.Add(dependantResource);
                }
            }
        }
        /// <summary>
        /// Validates the resource to ensure it should be included
        /// </summary>
        /// <param name="resource"></param>
        /// <returns></returns>
        private static bool IncludeResource(ApplicationResource resource)
        {
            var include = true;

            if (resource.Type == ResourceTypes.BizTalkBinding)
            {
                var bindingAdapter = BindingResourceAdapter.Create(resource);
                if (string.IsNullOrEmpty(bindingAdapter.SourceLocation))
                    include = false;
            }

            return include;
        }
        #endregion
    }
}